/*
lib.dm
All of the libraries used are found here.

dantom.db
audeuro.ini
deadron.texthandling
yota.shockwave
*/
/* How to use Yota.Shockwave...

 All that is needed, is the following line!

  new/shockwave(loc,icon)

 This creates a ring of the icon arround the loc, and flys outward.
 You may want some more control though.  Params like Ticks, Speed,
 and Density will give you that.

 Here is complete info on the paramiters...

                 [type] Name            (Default)  -Explanation
  new/shockwave(
                 [atom] Loc             (Required) -The centerpoint.
                 [icon] Icon            (Required) -The icon to be used.
                  [num] Ticks           (10)       -The number of ticks the ring will fly.  A negative number makes it unlimited.
                  [num] Speed           (20)       -The rate per tick the ring will fly out.
                  [num] Density         (20)       -The number of projectiles to shoot out.  Multiples of 4 are suggested for symmetry. (Note: This is NOT the same as the 'density' variable!)
              [boolean] StopAtMob       (0)        -Will the projectile dissapear when a mob is hit?
          [string|path] MobCall         (null)     -Will call the colliding mob's procedure.
   [(associative) list] MobCallArg      (list())   -These will be the arguments for the mob's call.
              [boolean] StopAtObj       (0)        -Will the projectile dissapear when an obj is hit?
          [string|path] ObjCall         (null)     -Will call the colliding obj's procedure.
   [(associative) list] ObjCallArg      (list())   -These will be the arguments for the obj's call.
              [boolean] StopAtDenseTurf (0)        -With this enabled, the blast will stop when a solid turf is hit.
          [string|path] TurfCall        (null)     -Will call the procedure of the turf the object is hovering.
   [(associative) list] TurfCallArg     (list())   -These will be the arguments for the turf's call.
                  [mob] Source          (null)     -This mob will take the roll as usr in called procedures.  When set to null, the projectile itself will become usr.
                                                     )

 Here's some misc. information, returning DELETE_PROJ in one of the
 procedures called from MobCall or ObjCall will delete they attacking
 projectile even if the StopAt.. params are false.
  DELETE_PROJ is a macro for "delSW".

 Also note that [(associative) list] does not mean the list must be
 associative.  It is optional.
*/

#define DELETE_PROJ "delSW"

shockwave/New(
 atom/Loc=usr,
 icon/Icon,
 Ticks=10,
 Speed=20,
 Density=20,
 StopAtMob=0,
 MobCall=null,
 list/MobCallArg=list(),
 StopAtObj=0,
 ObjCall=null,
 list/ObjCallArg=list(),
 TurfCall=null,
 list/TurfCallArg=list(),
 StopAtDenseTurf=0,
 mob/Source=null)
	var
		dirstep=180/Density*2
		lastdir=0
	if(isarea(Loc)) return
	while(!isturf(Loc)) Loc = Loc.loc
	while(lastdir<360)
		new/obj/shockwave(Loc,Icon,lastdir,Ticks,Speed,StopAtMob,MobCall,MobCallArg,StopAtObj,ObjCall,ObjCallArg,StopAtDenseTurf,TurfCall,TurfCallArg,Source)
		lastdir+=dirstep

obj/shockwave
	animate_movement=0
	New(
	 turf/Loc,
	 icon/Icon,
	 Direction,
	 Ticks,
	 Speed,
	 StopAtMob,
	 MobCall,
	 list/MobCallArg,
	 StopAtObj,
	 ObjCall,
	 list/ObjCallArg,
	 StopAtDenseTurf,
	 TurfCall,
	 list/TurfCallArg,
	 mob/Source)
		..(Loc)
		icon=Icon
		if(Source) usr=Source
		else usr=src
		var
			vel_x = 0
			vel_y = 0
			pos_x = pixel_x
			pos_y = pixel_y
			list/Called = new
		vel_x = cos(Direction) * Speed
		vel_y = sin(Direction) * Speed
		spawn
			while(Ticks!=0)
				pos_x+=vel_x
				pos_y+=vel_y
				if(pos_x > 16)
					pos_x -= 32
					x++
					if(!loc) del src
					if(TurfCall) if(!(loc in Called)) {call(loc,TurfCall)(arglist(TurfCallArg));Called+=loc}
					var/list/atom/movable/AL=new
					for(var/atom/movable/A in loc) if(!istype(A,/obj/shockwave)) AL += A
					AL += usr
					if(locate(/mob)in AL) if(MobCall) for(var/mob/M in loc) if(!(M in Called))
						if(call(M,MobCall)(arglist(MobCallArg)) == DELETE_PROJ && !StopAtMob) Ticks = 0
						Called+=M
					if(locate(/obj)in AL) if(ObjCall) for(var/obj/O in loc) if(!(O in Called))
						if(call(O,ObjCall)(arglist(ObjCallArg)) == DELETE_PROJ && !StopAtObj) Ticks = 0
						Called+=O
					if(locate(/mob)in AL) if(StopAtMob) del src
					if(locate(/obj)in AL) if(StopAtObj) del src
					if(StopAtDenseTurf) if(loc.density) del src
				if(pos_x < -16)
					pos_x += 32
					x--
					if(!loc) del src
					if(TurfCall) if(!(loc in Called)) {call(loc,TurfCall)(arglist(TurfCallArg));Called+=loc}
					var/list/atom/movable/AL=new
					for(var/atom/movable/A in loc) if(!istype(A,/obj/shockwave)) AL += A
					AL += Source
					if(locate(/mob)in AL) if(MobCall) for(var/mob/M in loc) if(!(M in Called))
						if(call(M,MobCall)(arglist(MobCallArg)) == DELETE_PROJ && !StopAtMob) Ticks = 0
						Called+=M
					if(locate(/obj)in AL) if(ObjCall) for(var/obj/O in loc) if(!(O in Called))
						if(call(O,ObjCall)(arglist(ObjCallArg)) == DELETE_PROJ && !StopAtObj) Ticks = 0
						Called+=O
					if(locate(/mob)in AL) if(StopAtMob) del src
					if(locate(/obj)in AL) if(StopAtObj) del src
					if(StopAtDenseTurf) if(loc.density) del src
				if(pos_y > 16)
					pos_y -= 32
					y++
					if(!loc) del src
					if(TurfCall) if(!(loc in Called)) {call(loc,TurfCall)(arglist(TurfCallArg));Called+=loc}
					var/list/atom/movable/AL=new
					for(var/atom/movable/A in loc) if(!istype(A,/obj/shockwave)) AL += A
					AL += Source
					if(locate(/mob)in AL) if(MobCall) for(var/mob/M in loc) if(!(M in Called))
						if(call(M,MobCall)(arglist(MobCallArg)) == DELETE_PROJ && !StopAtMob) Ticks = 0
						Called+=M
					if(locate(/obj)in AL) if(ObjCall) for(var/obj/O in loc) if(!(O in Called))
						if(call(O,ObjCall)(arglist(ObjCallArg)) == DELETE_PROJ && !StopAtObj) Ticks = 0
						Called+=O
					if(locate(/mob)in AL) if(StopAtMob) del src
					if(locate(/obj)in AL) if(StopAtObj) del src
					if(StopAtDenseTurf) if(loc.density) del src
				if(pos_y < -16)
					pos_y += 32
					y--
					if(!loc) del src
					if(TurfCall) if(!(loc in Called)) {call(loc,TurfCall)(arglist(TurfCallArg));Called+=loc}
					var/list/atom/movable/AL=new
					for(var/atom/movable/A in loc) if(!istype(A,/obj/shockwave)) AL += A
					AL += Source
					if(locate(/mob)in AL) if(MobCall) for(var/mob/M in loc) if(!(M in Called))
						if(call(M,MobCall)(arglist(MobCallArg)) == DELETE_PROJ && !StopAtMob) Ticks = 0
						Called+=M
					if(locate(/obj)in AL) if(ObjCall) for(var/obj/O in loc) if(!(O in Called))
						if(call(O,ObjCall)(arglist(ObjCallArg)) == DELETE_PROJ && !StopAtObj) Ticks = 0
						Called+=O
					if(locate(/mob)in AL) if(StopAtMob) del src
					if(locate(/obj)in AL) if(StopAtObj) del src
					if(StopAtDenseTurf) if(loc.density) del src
				pixel_x = pos_x
				pixel_y = pos_y
				sleep(1)
				if(Ticks>0) Ticks--
			del src

//cursors
#define Default_Cursor 0
#define Client_Cursor 1
#define Server_Cursor 2


//conversions
#define TEXT_CONV 1
#define RSC_FILE_CONV 2
#define NUMBER_CONV 3


//column flag values:
#define IS_NUMERIC 1
#define IS_BINARY 2
#define IS_NOT_NULL 4
#define IS_PRIMARY_KEY 8
#define IS_UNSIGNED 16


//types
#define TINYINT 1
#define SMALLINT 2
#define MEDIUMINT 3
#define INTEGER 4
#define BIGINT 5
#define DECIMAL 6
#define FLOAT 7
#define DOUBLE 8
#define DATE 9
#define DATETIME 10
#define TIMESTAMP 11
#define TIME 12
#define STRING 13
#define BLOB 14
// TODO: Investigate more recent type additions and see if I can handle them. - Nadrew


var
	DB_SERVER = "localhost" // This is the location of your MySQL server (localhost is USUALLY fine)
	DB_PORT = 3306 // This is the port your MySQL server is running on (3306 is the default)

DBConnection
	New(dbi_handler,username,password_handler,cursor_handler)
		src.dbi = dbi_handler
		src.user = username
		src.password = password_handler
		src.default_cursor = cursor_handler
		_db_con = _dm_db_new_con()
	proc
		Connect(dbi_handler=src.dbi,user_handler=src.user,password_handler=src.password,cursor_handler)
			if(!src) return 0
			cursor_handler = src.default_cursor
			if(!cursor_handler) cursor_handler = Default_Cursor
			return _dm_db_connect(_db_con,dbi_handler,user_handler,password_handler,cursor_handler,null)

		Disconnect() return _dm_db_close(_db_con)

		IsConnected() return _dm_db_is_connected(_db_con)

		Quote(str) return _dm_db_quote(_db_con,str)

		ErrorMsg() return _dm_db_error_msg(_db_con)
		SelectDB(database_name,dbi)
			if(IsConnected()) Disconnect()
			return Connect("[dbi?"[dbi]":"dbi:mysql:[database_name]:[DB_SERVER]:[DB_PORT]"]",user,password)
		NewQuery(sql_query,cursor_handler=src.default_cursor) return new/DBQuery(sql_query,src,cursor_handler)

	var
		_db_con // This variable contains a reference to the actual database connection.
		dbi // This variable is a string containing the DBI MySQL requires.
		user // This variable contains the username data.
		password // This variable contains the password data.
		default_cursor // This contains the default database cursor data.
		//
		server = "localhost"
		port = 3306

DBQuery
	New(sql_query,DBConnection/connection_handler,cursor_handler)
		if(sql_query) src.sql = sql_query
		if(connection_handler) src.db_connection = connection_handler
		if(cursor_handler) src.default_cursor = cursor_handler
		_db_query = _dm_db_new_query()
		return ..()

	proc

		Connect(DBConnection/connection_handler) src.db_connection = connection_handler

		Execute(sql_query=src.sql,cursor_handler=default_cursor)
			Close()
			return _dm_db_execute(_db_query,sql_query,db_connection._db_con,cursor_handler,null)

		NextRow() return _dm_db_next_row(_db_query,item,conversions)

		RowsAffected() return _dm_db_rows_affected(_db_query)

		RowCount() return _dm_db_row_count(_db_query)

		ErrorMsg() return _dm_db_error_msg(_db_query)

		Columns()
			if(!columns)
				columns = _dm_db_columns(_db_query,/DBColumn)
			return columns

		GetRowData()
			var/list/columns = Columns()
			var/list/results
			if(columns.len)
				results = list()
				for(var/C in columns)
					results+=C
					var/DBColumn/cur_col = columns[C]
					results[C] = src.item[(cur_col.position+1)]
			return results

		Close()
			item.len = 0
			columns = null
			conversions = null
			return _dm_db_close(_db_query)

		Quote(str)
			return db_connection.Quote(str)

		SetConversion(column,conversion)
			if(istext(column)) column = columns.Find(column)
			if(!conversions) conversions = new/list(column)
			else if(conversions.len < column) conversions.len = column
			conversions[column] = conversion

	var
		sql // The sql query being executed.
		default_cursor
		list/columns //list of DB Columns populated by Columns()
		list/conversions
		list/item[0]  //list of data values populated by NextRow()

		DBConnection/db_connection
		_db_query

DBColumn
	var
		name
		table
		position //1-based index into item data
		sql_type
		flags
		length
		max_length

	New(name_handler,table_handler,position_handler,type_handler,flag_handler,length_handler,max_length_handler)
		src.name = name_handler
		src.table = table_handler
		src.position = position_handler
		src.sql_type = type_handler
		src.flags = flag_handler
		src.length = length_handler
		src.max_length = max_length_handler
		return ..()

	proc
		SqlTypeName(type_handler=src.sql_type)
			switch(type_handler)
				if(TINYINT) return "TINYINT"
				if(SMALLINT) return "SMALLINT"
				if(MEDIUMINT) return "MEDIUMINT"
				if(INTEGER) return "INTEGER"
				if(BIGINT) return "BIGINT"
				if(FLOAT) return "FLOAT"
				if(DOUBLE) return "DOUBLE"
				if(DATE) return "DATE"
				if(DATETIME) return "DATETIME"
				if(TIMESTAMP) return "TIMESTAMP"
				if(TIME) return "TIME"
				if(STRING) return "STRING"
				if(BLOB) return "BLOB"



				//INI

				/*
ini_reader
	Variables
		Mode
			INIREADER_CFG - 0, This switches it to CFG (Default)
			INIREADER_INI - 1, This switches it to INI
		cLastReadLine - This is the last line that was read in the file. It is included for debugging purposes.
		list
			Config - This is the current Configuration File.
	Procedures
		New( F ) - If F is either a file, or a file name, it calls LoadConfiguration( F ). Else, it initializes Config.
		Del() - This sets Config to null.
		Trim( T ) - If T is a non-null text string, it will strip any leading or trailing spaces.
		Replace( T, What, With ) - Replaces What in T with With.
		String( Num, Char ) - Returns Num Chars.
		Escaped( T ) - Replaces \ with \\,  Carriage Return with
		CurrentScanLine() - Returns cLastReadLine
		InputConfiguration( Configuration ) - Removes in Configuration Carriage Returns and Tabs, then fills Config by calling LoadStructure.
		LoadConfiguration( Configuration ) - Loads in Configuration, then removes from the data Carriage Returns and Tabs, then fills Config by calling LoadStructure.
		LoadStructure( Number/N, data, e = 0 ) - Goes through data and sets up a Dictionary to match the configuration file exactly.
		NewConfiguration() - Resets Config and cLastReadLine
		OutputConfiguration( NewLine = CrLf, Whitespace = 1 ) - Returns OutputDictionary.
		OutputDictionary( Dictionary/Dict, Level, NewLine = CrLf, Whitespace = 1 ) - Returns the .ini format of the dictionary.
		ReadSetting( Setting, Default ) - Returns Setting in Config, or Default if it isn't found.
		RemoveSetting( Setting ) - Removes Setting in Config, if it exists.
		Root() - Returns a Deep Copy of Config.
		SaveConfiguration( FileName, NewLine = CrLf, Whitespace = 0 ) - Saves OutputDictionary's return to a file.
		WriteSetting( Setting, Val ) - Writes Val to Setting in Config, or overrides Setting with Val if it exists.
*/


#define Cr ascii2text(13)
#define Lf ascii2text(10)
#define CrLf ascii2text(13) + ascii2text(10)
#define Tab ascii2text(9)
#define islist( L ) istype( L, /list )


var
	const
		BEGINOP_INI = 91		// [
		ENDOP_INI = 93			// ]
		BEGINOP_CFG = 123		// {
		ENDOP_CFG = 125			// }

		LISTBEGIN = 60			// <
		LISTEND = 62			// >
		LISTDELIM = 44			// ,
		ASSIGNOP = 61			// =
		TERMINATEOP = 59		// ;
		STRINGOP = 34			// "
		COMMENT = "//"			//
		COMMENT_ALT = ";"		//
		BEGINCOMMENT = "/*"		//
		ENDCOMMENT = "*/"		//


		WS_SPACE = 32			// space
		WS_LINEFEED = 10		// linefeed
		WS_TAB = 9				//
		WS_RETURN = 13			//

		ESCAPESEQ = 92 			// Double Baskslash
		ES_NEWLINE = 110		// n
		ES_RETURN = 114			// r
		ES_TAB = 116			// t
		ES_QUOTE = 34			// "
		ES_BACKSLASH = 92		// Backslash

		INIREADER_CFG = 1		// Use CFG
		INIREADER_INI = 2		// Use INI

proc
	DeepCopy( list/L )
		. = new/list
		if( istype( L ) && L && L.len )
			for( var/i in L )
				if( islist( i ) )
					var/list/N = new
					N = DeepCopy( L[i] )
					.[i] = N
				else
					.[i] = L[i]
	Pos( list/L, Key = 1)
		. = 0
		if( islist( L ) )
			for( var/i in L )
				.++
				if( i == Key )
					return
		. = 0

slist
	var
		list/Item = null
	New()
		Item = new

ini_reader
	var
		Mode = INIREADER_CFG
		cLastReadLine = 0
		fspec = 0
		list
			Config = null
	New()
		..()
		NewConfiguration()
	New( F, INIMode )
		..()
		Mode = INIMode
		if( !Mode ) Mode = initial(Mode)
		if( istext( F ) && fexists( F ) )
			F = file(F)
		if( isfile( F ) )
			LoadConfiguration(F)
	Del()
		Config = null
		..()
	proc
		Trim( T )
			if( ckey(T) && istext( T ) )
				. = T
				while( text2ascii(copytext( ., 1, 2 )) <= 32 )
					. = copytext( ., 2 )
				while( text2ascii(copytext( ., length( . ))) <= 32  )
					. = copytext( ., 1, length( . ) )
		Replace( T, What, With )
			. = T
			if( istext( T ) && T && istext( What ) && istext( With )  )
				. = dd_replacetext( T, What, With )
		String( Num, Char )
			. = ""
			if( isnum(Num) && istext(Char) && Num > 0 && length(ckey(Char)))
				for( var/i = 1; i <= Num; i++ )
					. += Char
		Escaped( T )
			if( istext( T ) && ckey( T ) )
				. = Replace( T, "\\", "\\\\" )
				. = Replace( ., Lf, "\\n" )
				. = Replace( ., Cr, "\\r" )
				. = Replace( ., Tab, "\\t" )
				. = Replace( ., "\"", "\\\"" )
		CurrentScanLine()
			. = cLastReadLine
		InputConfiguration( Configuration )
			if( istext( Configuration ) && ckey( Configuration ) )
				Configuration = Replace( Configuration, ascii2text( WS_RETURN ), "" )
				Configuration = Replace( Configuration, ascii2text( WS_TAB ), "" )
				cLastReadLine = 1
				Config = LoadStructure( list(0), Configuration )
		LoadConfiguration( File )
			if( !isfile(File) && fexists( File ) )
				File = file (File)
			if( isfile( File ) )
				var
					Data
				Data = file2text( File )
				Data = Replace( Data, ascii2text( WS_RETURN ), "" )
				Data = Replace( Data, ascii2text( WS_TAB ), "" )
				cLastReadLine = 1
				Config = LoadStructure( list(0), Data )
		ListParse( list/N, data , end )
			var
				p = N[1]
				c
				ca
				sd
				nd
				np
				seq
				newval
				list/L = new
				slist/Return = new
			while( p < length(data) )
				p++
				if( p == end )
					break
				c = copytext( data, p, p + 1 )
				ca = text2ascii(c)
				if( sd )
					if( seq )
						switch( ca )
							if( ES_BACKSLASH )
								newval += "\\"
							if( ES_NEWLINE )
								newval += ascii2text(WS_LINEFEED)
							if( ES_QUOTE )
								newval += "\""
							if( ES_RETURN )
								newval += ascii2text(WS_RETURN)
							if( ES_TAB )
								newval += ascii2text(WS_TAB)
							else
								if( isnum(text2num(c)) )
									np = copytext( data, p, p + 3)
									newval += ascii2text(text2num(np))
									p += 2
								else
									newval += c
						seq = 0
					else
						if( ca == ESCAPESEQ )
							seq = 1
						else if( ca == STRINGOP )
							L += newval
							sd = 0
							newval = ""
						else if( ca == WS_LINEFEED )
							CRASH( "Invalid Assignment. Did you forget to close your list?" )
						else
							newval += c
				else if( nd )
					if( ca == WS_SPACE || ca == LISTDELIM || ca == LISTEND )
						L += text2num(newval)
						nd = 0
						newval = ""
					else
						newval += c
				else
					if( ca == STRINGOP )
						sd = 1
					else if( isnum(text2num(c)) || c == "-" || c == "." || lowertext(c) == "e" || c == "&" )
						nd = 1
						p--
					else if( ca == LISTBEGIN )
						var/list/O = list(p)
						L.len += 1
						L[L.len] = ListParse( O, data, findtext( data, ascii2text(LISTEND) , p ) +1 )
						p = O[1]
			Return.Item = L
			. = Return
			N[1] = p
		LoadStructure( list/N, data, e = 0 )
			if( istype(N) && N && istext( data ) && ckey( data ) )
				var
					p = N[1]
					c
					ca
					np
					sd
					nd
					seq
					assign
					newkey
					newval
					list/Dict = new
				while( p < length( data ) )
					p++
					if( p == e ) break
					c = copytext( data, p, p + 1 )
					ca = text2ascii(c)
					if( nd )
						if( ca == TERMINATEOP )
							Dict[Trim(newkey)] = text2num(newval)
							newkey = ""
							newval = ""
							nd = 0
							assign = 0
						else if( ca == WS_LINEFEED )
							cLastReadLine++
						else
							newval += c
					else if( sd )
						if( seq )
							switch( ca )
								if( ES_BACKSLASH )
									newval += "\\"
								if( ES_NEWLINE )
									newval += ascii2text(WS_LINEFEED)
								if( ES_QUOTE )
									newval += "\""
								if( ES_RETURN )
									newval += ascii2text(WS_RETURN)
								if( ES_TAB )
									newval += ascii2text(WS_TAB)
								else
									if( ca >= 48 && ca <= 57 )
										np = copytext( data, p, p + 3 )
										newval += ascii2text(text2num(np))
										p += 2
									else
										newval += c
							seq = 0
						else
							if( ca == ESCAPESEQ )
								seq = 1
							else if( ca == STRINGOP )
								Dict[ Trim(newkey) ] = newval
								sd = 0
								newkey = ""
								newval = ""
							else if( ca == WS_LINEFEED )
								cLastReadLine++
							else
								newval += c
					else if( assign )
						if( ca == STRINGOP )
							sd = 1
						else if( isnum(text2num(c)) || c == "-" || c == "." || lowertext(c) == "e" || c == "&" )
							nd = 1
							p--
						else if( ca == WS_LINEFEED )
							cLastReadLine++
						else if(ca == TERMINATEOP)
							assign = 0
						else if( ca == LISTBEGIN )
							var/list/O = list(p)
							Dict[Trim(newkey)] = ListParse( O, data, findtext( data, ascii2text(TERMINATEOP), p ) - 1)
							p = O[1]
							newkey = ""
						else if( ca <> WS_SPACE )
							CRASH( "Invalid Assignment. Forget an assignment operator?" )
					else
						switch( ca )
							if( BEGINOP_INI )
								continue
							if( BEGINOP_CFG )
								if( findtext( Trim(newkey), " " ) )
									CRASH( "Spaces are not allowed in key names." )
								else
									var/list/O = list(p)
									Dict[ Trim( newkey ) ] = LoadStructure( O, data )
									p = O[1]
									newkey = ""
							if( ENDOP_INI )
								if( Mode == INIREADER_INI )
									if( findtext( Trim(newkey), " ") )
										CRASH( "Spaces are not allowed in section names." )
									else
										var/list/O = list(p)
										Dict[Trim(newkey)] = LoadStructure( O, data , findtext( data, ascii2text(BEGINOP_INI), p + 1) )
										p = O[1]
										newkey = ""
										if( findtext( data, ascii2text( BEGINOP_INI ), p ) )
											p = findtext( data, ascii2text( BEGINOP_INI ), p )
										else
											break
							if( ENDOP_CFG )
								if( Mode == INIREADER_CFG )
									break
							if( ASSIGNOP )
								if( findtext( Trim( newkey ), " " ) )
									CRASH( "Spaces are not allowed in key names." )
								else
									assign = 1
							if( TERMINATEOP )
								if(newval || newkey)
									Dict[ Trim(newkey) ] = newval
									newkey = ""
									newval = ""
								else if(!newval && !newkey)
									np = findtext( data, ascii2text(WS_LINEFEED), p )
									if( np )
										cLastReadLine++
										p = np
									else
										p = length(data)
										break
							if( WS_LINEFEED )
								cLastReadLine++
								newkey += ascii2text( WS_SPACE )
							if( text2ascii(COMMENT), text2ascii(BEGINCOMMENT) )
								if( copytext( data, p, p + 2 ) == COMMENT )
									np = findtext( data, ascii2text(WS_LINEFEED), p )
									if( np )
										cLastReadLine++
										p = np
									else
										p = length(data)
										break
								else if( copytext( data, p, p + 2 ) == BEGINCOMMENT )
									np = findtext( data, ENDCOMMENT, p )
									if( np )
										var/list/L = dd_text2list( copytext( data, p, p + ( np - p ) ), ascii2text(WS_LINEFEED) )
										cLastReadLine += L.len
										p = np + 1
									else
										p = length(data)
										break
							else
								newkey += c
				. = Dict
				N[1] = p
		NewConfiguration()
			cLastReadLine = 1
			Config = new
		OutputConfiguration( NewLine = Lf, Whitespace = 1 )
			. = OutputDictionary( Config, 0, NewLine, Whitespace )
		OutputDictionary( list/Dict, Level, NewLine = Lf, Whitespace = 1 )
			var
				lt
				sp
				data
			if( istype( Dict ) && Dict )
				if( Dict.len )
					if( Whitespace )
						lt = String( Level, Tab )
						sp = " "
					for( var/i in Dict )
						if( islist(Dict[i]) )
							if( Whitespace )
								data += lt + NewLine
							if( Mode == INIREADER_INI )
								data += ascii2text(BEGINOP_INI)
								data += i
								data += ascii2text(ENDOP_INI) + NewLine
								data += OutputDictionary(Dict[i], Level, NewLine, Whitespace, i)
							else
								data += lt + i + NewLine
								data += lt + ascii2text(BEGINOP_CFG) + NewLine
								data += OutputDictionary(Dict[i], lt + 1, NewLine, Whitespace )
								data += lt + ascii2text(ENDOP_CFG) + NewLine
							if( Whitespace )
								data += NewLine
						else if( isnum(Dict[i]) )
							data += i + sp + ascii2text(ASSIGNOP) + sp + "[Dict[i]]" + ascii2text(TERMINATEOP) + NewLine
						else if( istype( Dict[i], /slist ) )
							var/slist/I = Dict[i]
							data += i + sp + ascii2text(ASSIGNOP) + sp + ascii2text(LISTBEGIN) + List2Text(I.Item) + ascii2text(LISTEND) + ascii2text(TERMINATEOP) + NewLine
						else if(i)
							data += i + sp + ascii2text(ASSIGNOP) + sp + ascii2text(STRINGOP) + Escaped( Dict[i] ) + ascii2text(STRINGOP) + ascii2text(TERMINATEOP) + NewLine
						if( Pos(Dict, i) < Dict.len )
							if( islist(Dict[i]) )
								data += NewLine
			. = data
		List2Text( list/L, Delimiter = "," )
			for( var/i = 1; i <= L.len; i++ )
				if( istype(L[i], /slist) )
					var/slist/I = L[i]
					L[i] = ascii2text(LISTBEGIN) + List2Text(I.Item,Delimiter) + ascii2text(LISTEND)
				else if( isnum( L[i] ) )
					L[i] = num2text(L[i])
				else if( istext( L[i] ) )
					L[i] = ascii2text(STRINGOP) + Escaped(L[i]) + ascii2text(STRINGOP)
			. = dd_list2text(L,Delimiter)
		ReadSetting( Setting, Default )
			if( istext( Setting ) && length( ckey( Setting ) ) && Config )
				if( Setting in Config )
					if( istype( Config[Setting], /slist ) )
						var/slist/I = Config[Setting]
						. = I.Item
					else
						. = Config[Setting]
					return
			. = Default
		RemoveSetting( Setting )
			if( istext( Setting ) && length( ckey( Setting ) ) )
				if( Setting in Config )
					Config -= Setting
		Root()
			. = DeepCopy( Config )
		SaveConfiguration( FileName, NewLine = Lf, Whitespace = 0 )
			var
				Data
			if( Mode == INIREADER_CFG )
				Data = OutputDictionary( Config, 1, NewLine, Whitespace )
			else
				Data = OutputDictionary( Config, 0, NewLine, Whitespace )
			if( fexists( FileName ) )
				fdel(FileName)
			text2file( Data, FileName )
		WriteSetting( Setting, Val )
			if( istext( Setting ) && ckey( Setting ) && !isnull(Val))
				if( Config[ Setting ] )
					if( islist(Val) )
						var/list/Dict = Val
						Config[Setting] = DeepCopy(Dict)
					else
						Config[Setting] = Val
				else
					Config[Setting] = Val



					//TEXTPROC

proc
	reversetext(string)
		for(var/i=length(string), i>0, --i)
			. += ascii2text(text2ascii(string,i))

	explodetext(string, delim, limit=0)
		. = new/list
		var/pos = findtext(string, delim), tpos, str
		if(limit > 1)
			var/ct = 1
			if(pos != 1)
				. += copytext(string, 1, pos)
				ct = 2
			while(ct < limit && pos)
				tpos = pos+1
				pos = findtext(string, delim, tpos)
				str = copytext(string, tpos, pos)
				if(str)
					. += str
					++ct
			str = copytext(string, pos+1)
			if(str)
				. += str
		else if(limit == 1)
			. += string
		else
			if(pos != 1)
				. += copytext(string, 1, pos)
			while(pos)
				tpos = pos+1
				pos = findtext(string, delim, tpos)
				str = copytext(string, tpos, pos)
				if(str) . += str

	explodeText(string, delim, limit=0)
		. = new/list
		var/pos = findtextEx(string, delim), tpos, str
		if(limit > 1)
			var/ct = 1
			if(pos != 1)
				. += copytext(string, 1, pos)
				ct = 2
			while(ct < limit && pos)
				tpos = pos+1
				pos = findtextEx(string, delim, tpos)
				str = copytext(string, tpos, pos)
				if(str)
					. += str
					++ct
			str = copytext(string, pos+1)
			if(str)
				. += str
		else if(limit == 1)
			. += string
		else
			if(pos != 1)
				. += copytext(string, 1, pos)
			while(pos)
				tpos = pos+1
				pos = findtextEx(string, tpos, pos)
				if(str) . += str

	implodetext(list/L, delim)
		if(L.len)
			. = L[1]
			for(var/i in 2 to L.len)
				. += delim + L[i]

///////////////////
// TEXT HANDLING //
///////////////////
/*
 This text handling library is a Deadron core library,
 providing some helpful text functions not found in BYOND.

 To include this library, click the library checkbox in the
 Lib folder of the file tree, or add this line in your code:

#include <deadron/texthandling>

 If you have suggestions or questions, please email
 ron@deadron.com.

 Copyright (c) 1999, 2000, 2001, 2002, 2003 Ronald J. Hayden. All rights reserved.

 09/12/03: Integrated Crispy's changes so dd_text2list() supports non-text items in list.
 02/10/02: Added dd_centertext() and dd_limittext().

dd_file2list(file_path, separator = "\n")
	Splits the text from the specified file into a list.
	file_path is the path to the file.
	separator is an optional delimiter between items in the file;
	it defaults to "\n", which makes each line of the file an item in the list.

	Example:

	// Read in the list of possible NPC names.
	var/list/names = dd_file2list("NPCs.txt")

dd_replacetext(text, search_string, replacement_string)
	Returns a new string replacing all occurrences of search_string in text
	with replacement_string. This is not case-sensitive.

	Example:

	verb/say(msg as text)
		// Don't let the player fake people out using line breaks when they say things.
		// Replace any instances of <BR> or /n with a space.
		var/search_string = "<BR>"
		var/replacement = " "
		var/sanitized_text = dd_replacetext(msg, search_string, replacement)

		search_string = "/n"
		sanitized_text = dd_replacetext(sanitized_text, search_string, replacement)

		view(src) << sanitized_text
		return

dd_replaceText(text, search_string, replacement_string)
	The case-sensitive version of dd_replacetext().

dd_hasprefix(text, prefix)
	Returns 1 if the text has the specified prefix, 0 otherwise.  This version is not case sensitive.

	Example:

	// Does the player's name have GM as the prefix?
	if (dd_hasprefix(name, "GM"))
		// Give them GM abilities.

dd_hasPrefix(text, prefix)
	The case-sensitive version of dd_hasprefix.

dd_hassuffix(text, suffix)
	Returns 1 if the text has the specified prefix, 0 otherwise.
	This version is not case sensitive.

dd_hasSuffix(text, suffix)
	Returns 1 if the text has the specified prefix, 0 otherwise.
	This version is case sensitive.

dd_text2list(text, separator)
	Split the text into a list, where separator is the delimiter between items.
	Returns the list. This is not case-sensitive.

	If the myText string is "a = b = c", and you call dd_text2list(myText, " = "), you get a list back with these items:
		a
		b
		c

	Example:

	// Get a list containing the names in this string.
	var/mytext = "George; Bernard; Shaw"
	var/separator = "; "
	var/list/names = dd_text2list(mytext, separator)

dd_text2List(text, separator)
	The case-sensitive version of dd_text2list().

dd_list2text(list/the_list, separator)
	Create a string by combining each element of the list,
	inserting the separator between each item.

	Example:

	// Turn this list of names into one string separated by semi-colons.
	var/list/names = list("George", "Bernard", "Shaw")
	var/separator = "; "
	var/mytext = dd_list2text(names, separator)

dd_centertext(message, length)
	Returns a new text string, centered based on the length.

	If the string is not as long as the length, spaces are added
	on both sides of the message.

	If the string is longer than the specified length, the message
	is truncated to fit to the length.

	This function is useful when laying out text on the map, where you
	might want to center a title, for example.

dd_limittext(message, length)
	If the message is longer than length, truncates the message to fit
	length. This is useful for text on the map, where you might want
	to display a player name, for example, but have to make sure it's
	not too long to fit.
*/




proc
	///////////////////
	// Reading files //
	///////////////////
	dd_file2list(file_path, separator = "\n")
		var/file
		if (isfile(file_path))
			file = file_path
		else
			file = file(file_path)
		return dd_text2list(file2text(file), separator)


    ////////////////////
    // Replacing text //
    ////////////////////
	dd_replacetext(text, search_string, replacement_string)
		// A nice way to do this is to split the text into an array based on the search_string,
		// then put it back together into text using replacement_string as the new separator.
		var/list/textList = dd_text2list(text, search_string)
		return dd_list2text(textList, replacement_string)


	dd_replaceText(text, search_string, replacement_string)
		var/list/textList = dd_text2List(text, search_string)
		return dd_list2text(textList, replacement_string)


    /////////////////////
	// Prefix checking //
	/////////////////////
	dd_hasprefix(text, prefix)
		var/start = 1
		var/end = lentext(prefix) + 1
		return findtext(text, prefix, start, end)

	dd_hasPrefix(text, prefix)
		var/start = 1
		var/end = lentext(prefix) + 1
		return findtextEx(text, prefix, start, end)


    /////////////////////
	// Suffix checking //
	/////////////////////
	dd_hassuffix(text, suffix)
		var/start = length(text) - length(suffix)
		if (start) return findtext(text, suffix, start)

	dd_hasSuffix(text, suffix)
		var/start = length(text) - length(suffix)
		if (start) return findtextEx(text, suffix, start)

	/////////////////////////////
	// Turning text into lists //
	/////////////////////////////
	dd_text2list(text, separator)
		var/textlength      = lentext(text)
		var/separatorlength = lentext(separator)
		var/list/textList   = new /list()
		var/searchPosition  = 1
		var/findPosition    = 1
		var/buggyText
		while (1)															// Loop forever.
			findPosition = findtext(text, separator, searchPosition, 0)
			buggyText = copytext(text, searchPosition, findPosition)		// Everything from searchPosition to findPosition goes into a list element.
			textList += "[buggyText]"										// Working around weird problem where "text" != "text" after this copytext().

			searchPosition = findPosition + separatorlength					// Skip over separator.
			if (findPosition == 0)											// Didn't find anything at end of string so stop here.
				return textList
			else
				if (searchPosition > textlength)							// Found separator at very end of string.
					textList += ""											// So add empty element.
					return textList

	dd_text2List(text, separator)
		var/textlength      = lentext(text)
		var/separatorlength = lentext(separator)
		var/list/textList   = new /list()
		var/searchPosition  = 1
		var/findPosition    = 1
		var/buggyText
		while (1)															// Loop forever.
			findPosition = findtextEx(text, separator, searchPosition, 0)
			buggyText = copytext(text, searchPosition, findPosition)		// Everything from searchPosition to findPosition goes into a list element.
			textList += "[buggyText]"										// Working around weird problem where "text" != "text" after this copytext().

			searchPosition = findPosition + separatorlength					// Skip over separator.
			if (findPosition == 0)											// Didn't find anything at end of string so stop here.
				return textList
			else
				if (searchPosition > textlength)							// Found separator at very end of string.
					textList += ""											// So add empty element.
					return textList

	dd_list2text(list/the_list, separator)
		var/total = the_list.len
		if (total == 0)														// Nothing to work with.
			return

		var/newText = "[the_list[1]]"										// Treats any object/number as text also.
		var/count
		for (count = 2, count <= total, count++)
			if (separator) newText += separator
			newText += "[the_list[count]]"
		return newText

	dd_centertext(message, length)
		var/new_message = message
		var/size = length(message)
		if (size == length)
			return new_message
		if (size > length)
			return copytext(new_message, 1, length + 1)

		// Need to pad text to center it.
		var/delta = length - size
		if (delta == 1)
			// Add one space after it.
			return new_message + " "

		// Is this an odd number? If so, add extra space to front.
		if (delta % 2)
			new_message = " " + new_message
			delta--

		// Divide delta in 2, add those spaces to both ends.
		delta = delta / 2
		var/spaces = ""
		for (var/count = 1, count <= delta, count++)
			spaces += " "
		return spaces + new_message + spaces

	dd_limittext(message, length)
		// Truncates text to limit if necessary.
		var/size = length(message)
		if (size <= length)
			return message
		else
			return copytext(message, 1, length + 1)

